<?php
/**
 * User: YL
 * Date: 2019/09/12
 */

namespace App\Middleware;

use Closure;
use Illuminate\Http\Request;
use restful\ResultException;
use restful\traits\RedisHandler;
use restful\traits\ResultThrow;
use restful\utils\Env;
use restful\utils\Signature;

class CheckSignature
{
    use ResultThrow;
    use RedisHandler;

    /**
     * Handle an incoming request.
     *
     * @param Request $request
     * @param Closure $next
     * @return mixed
     * @throws ResultException
     */
    public function handle(Request $request, Closure $next)
    {
        if (! Env::get('check_signature')) {
            return $next($request);
        }

        // 判断时间戳是否超时
        $timeout = Env::get('timestamp_timeout', 60);
        $timestamp = $request->input('timestamp',0);
        $time = time();
        if ($timestamp > $time || ($timestamp + $timeout) < $time) {
            $this->error('请求超时~');
        }

        // 判断随机数是否有效
        $nonce = $request->input('nonce', '');
        $this->validateNonce($nonce, $timestamp, $timeout);

        // 验证签名是否正确
        $sign = $request->input('sign', '');
        $data = $request->params ?? [];
        $data['timestamp'] = $timestamp;
        $data['nonce'] = $nonce;
        $key = Env::get('signature_key', '');
        if (! Signature::verify($sign, $data, $key)) {
            $this->error('签名验证失败~');
        }

        return $next($request);
    }

    /**
     * 验证随机数
     * @param string $nonce
     * @param int $timestamp
     * @param int $timeout
     * @throws ResultException
     */
    protected function validateNonce(string $nonce, int $timestamp, int $timeout)
    {
        if (empty($nonce)) {
            $this->error('请求随机数不存在~');
        }

        // 保存的随机数
        $nonce .= $timestamp;

        // 缓存标识
        $cacheKey = 'nonce-list-' . $timeout;

        // 获取已缓存随机数列表
        $list = $this->getRedisHandler()->lrange($cacheKey, 0, -1);
        if (in_array($nonce, $list)) {
            $this->error('请求随机数已存在~');
        }

        // 添加随机数
        $this->getRedisHandler()->lpush($cacheKey, $nonce);

        // 设置过期时间
        if ($this->getRedisHandler()->ttl($cacheKey) == -1) {
            $this->getRedisHandler()->expire($cacheKey, $timeout);
        }
    }
}
